﻿#region Usings

using System;
using System.Collections;
using System.Diagnostics;
using System.Reflection;

using Gonte.Collections; 

#endregion

namespace Gonte.Reflection
{
    /// <summary>
    /// Helper to operate on collections using reflection
    /// </summary>
    public static class CollectionHelper
    {
        #region Methods

        #region Adding items

        public static void AddItem(Object owner, string propertyName, Object collection, Object item)
        {
            ExceptionThrower.ThrowWhenNot(
                collection is IList || collection is IDictionary,
                string.Format("AddItem is not implemented for object of type: '{0}'", collection.GetType().Name));

            if (collection is IList)
            {
                AddItemToList((IList)collection, item);
            }
            else if (collection is IDictionary)
            {
                AddItemToDictionary(owner, propertyName, (IDictionary)collection, item);
            }
        }

        public static void AddItemToList(IList list, Object item)
        {
            list.Add(item);
        }

        public static void AddItemToDictionary(Object owner, string propertyName, IDictionary dictionary, Object item)
        {
            ExceptionThrower.ThrowWhenNull("owner", owner);
            ExceptionThrower.ThrowWhenNullOrEmpty("propertyName", propertyName);

            PropertyInfo propertyInfo = owner.GetType().GetProperty(propertyName);
            DictionaryItemAttribute attribute = CustomAttribute.GetCustomAttribute<DictionaryItemAttribute>(propertyInfo, false);

            ExceptionThrower.ThrowWhenNull("DictionaryItemAttribute", attribute);

            Object key = GetDictionaryItemKey(item, attribute);
            dictionary.Add(key, item);
        }

        #endregion

        #region Removing items

        public static void RemoveItem(Object owner, string propertyName, Object collection, Object item)
        {
            ExceptionThrower.ThrowWhenNot(
                collection is IList || collection is IDictionary,
                string.Format("RemoveItem is not implemented for object of type: '{0}'", collection.GetType().Name));

            if (collection is IList)
            {
                RemoveItemFromList((IList)collection, item);
            }
            else if (collection is IDictionary)
            {
                RemoveItemFromDictionary(owner, propertyName, (IDictionary)collection, item);
            }
        }

        public static void RemoveItemFromList(IList list, Object item)
        {
            list.Remove(item);
        }

        public static void RemoveItemFromDictionary(Object owner, string propertyName, IDictionary dictionary, Object item)
        {
            ExceptionThrower.ThrowWhenNull("owner", owner);
            ExceptionThrower.ThrowWhenNullOrEmpty("propertyName", propertyName);

            PropertyInfo propertyInfo = owner.GetType().GetProperty(propertyName);
            DictionaryItemAttribute attribute = CustomAttribute.GetCustomAttribute<DictionaryItemAttribute>(propertyInfo, false);

            ExceptionThrower.ThrowWhenNull("DictionaryItemAttribute", attribute);

            Object key = GetDictionaryItemKey(item, attribute);
            dictionary.Remove(key);
        }

        #endregion

        #region Swapping items

        public static void SwapItems(Object collection, int oldIndex, int newIndex)
        {
            ExceptionThrower.ThrowWhenNot(
                collection is IList,
                string.Format("Can not swap items for object of type: '{0}'. Make sure that is a list", collection.GetType().Name));

            IList list = (IList)collection;
            Object item = list[oldIndex];
            list[oldIndex] = list[newIndex];
            list[newIndex] = item;
        }

        #endregion 

        #region Item replacemements

        public static void ReplaceItem(Object owner, string propertyName, Object collection, Object oldItem, Object newItem)
        {
            ExceptionThrower.ThrowWhenNot(
                collection is IList || collection is IDictionary,
                string.Format("ReplaceItem is not implemented for object of type: '{0}'", collection.GetType().Name));

            if (collection is IList)
            {
                ReplaceItemInList((IList)collection, oldItem, newItem);
            }
            else if (collection is IDictionary)
            {
                ReplaceItemInDictionary(owner, propertyName, (IDictionary)collection, oldItem, newItem);
            }
        }

        public static void ReplaceItemInList(IList list, Object oldItem, Object newItem)
        {
            int i = list.IndexOf(oldItem);
            ExceptionThrower.ThrowWhen(i == -1, string.Format("Item: '{0}' is not in the list", oldItem.ToString()));

            list[i] = newItem;
        }

        public static void ReplaceItemInDictionary(Object owner, string propertyName, IDictionary dictionary, Object oldItem, Object newItem)
        {
            ExceptionThrower.ThrowWhenNull("owner", owner);
            ExceptionThrower.ThrowWhenNullOrEmpty("propertyName", propertyName);

            PropertyInfo propertyInfo = owner.GetType().GetProperty(propertyName);
            DictionaryItemAttribute attribute = CustomAttribute.GetCustomAttribute<DictionaryItemAttribute>(propertyInfo, false);

            ExceptionThrower.ThrowWhenNull("DictionaryItemAttribute", attribute);

            Object key = GetDictionaryItemKey(oldItem, attribute);
            dictionary[key] = newItem;
        } 

        #endregion

        public static Object GetDictionaryItemKey(Object item, DictionaryItemAttribute attribute)
        {
            TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(item.GetType());
            Object key;

            if (attribute.KeyValue != null) // We have a value
            {
                key = attribute.KeyValue;
            }
            else // A value was not provided. get it from the item
            {
                key = typeAccessor.GetValue(item, attribute.KeyName);
            }

            Debug.WriteLine(string.Format("Key set to value: {0}", key));

            return key;
        }

        /// <summary>
        /// Retrieves the type of the item contained in the collection
        /// </summary>
        /// <param name="type">The collection type</param>
        /// <returns>Returns the type of the item in the collection</returns>
        public static Type GetItemType(Type type)
        {
            if (type.IsGenericType)
            {
                Type[] genericArguments = type.GetGenericArguments();

                if (type.GetInterface("IDictionary") != null
                    && genericArguments.Length > 1) // The NET Dictionary
                {
                    return genericArguments[1]; // Return the type of the value
                }

                return genericArguments[0];
            }
            else if (type.IsArray)
            {
                return type.GetElementType();
            }

            // If it is not generic it can be any type of object
            throw new InvalidOperationException(string.Format("Unable to get the type for the item of collection: {0}", type.FullName));
        }

        /// <summary>
        /// Retrieves the values of a collectiob
        /// </summary>
        /// <param name="collection"></param>
        /// <returns></returns>
        public static IEnumerable GetItemValues(IEnumerable collection)
        {
            IDictionary dictionary = collection as IDictionary;

            if (null != dictionary) // Special case when it is a dictionary
            {
                return dictionary.Values;
            }

            return collection;
        }

        #endregion
    }
}
